package com.efrei.schoolmanager.model;


import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.table.AbstractTableModel;

import com.efrei.schoolmanager.controlers.ControleurNote;
import com.efrei.schoolmanager.exceptions.LogicException;
import com.efrei.schoolmanager.model.obj.Classe;
import com.efrei.schoolmanager.model.obj.Eleve;
import com.efrei.schoolmanager.model.obj.Matiere;
import com.efrei.schoolmanager.model.obj.Note;
import com.efrei.schoolmanager.tools.Tools;

/**
 * gere un tableau editable permettant de visualiser/remplir les notes pour une classe
 */
public class ModeleListeNotes extends AbstractTableModel {
	private static final long serialVersionUID = -3927902702730908201L;
	
	private ControleurNote controleur;
	private List<String> entetes = new ArrayList<String>();
	
	//pour faciliter la gestion de l'affichage : READ ONLY
	private Classe classeCourante;
	private Matiere matiereCourante;
	private Map<Eleve, List<Note>>  notesByEleves;
	private List<Eleve> eleves;
	private Map<Integer, Integer> numControleByPeriode = new HashMap<Integer, Integer>();
	private int nbColumnsForNote = 0;
	//le nombre de fois ou ajouté note a été cliqué mais controle temporaire
	private int nbControleAdded = 0;
	
	public ModeleListeNotes(ControleurNote controleur) {
		super();
		this.controleur = controleur;
		
		entetes.add("Nom");
		entetes.add("Prenom");
		entetes.add("Moyenne");
		
		//on recup les notes
		refresh();
	}
	
	/**
	 * met à jour notes après ajout/suppression d'une colonne
	 * <br/> a appelé quand la matiere courante change
	 * (et donc aussi quand le couple classe-matiere courante a changé)
	 */
	public void refresh() {
		Classe nouvelleClasseCourante = controleur.getClasseCourante();
		Matiere nouvelleMatiereCourante = controleur.getMatiereCourante();
		
		if(nouvelleClasseCourante==null || nouvelleMatiereCourante==null) {
			if(nouvelleClasseCourante!=null) {
				//on recupere quand même les eleves
				eleves = new ArrayList<Eleve>(nouvelleClasseCourante.getEleves());
				fireTableRowsInserted(0, eleves.size()-1);
			}
			fireTableStructureChanged();
			return;
		}
		if(this.classeCourante!=nouvelleClasseCourante || this.matiereCourante!=nouvelleMatiereCourante) {
			nbControleAdded = 0;
			nbColumnsForNote = 0;
		}
		this.classeCourante = nouvelleClasseCourante;
		this.matiereCourante = nouvelleMatiereCourante;
		
		Set<Note> notes = controleur.getNotes();
		//construction d'une Map<Eleve,List<Note>> pour plus de facilité
		Eleve eleve;
		notesByEleves = new HashMap<Eleve, List<Note>>();
		numControleByPeriode = new HashMap<Integer, Integer>();
		for (Note note : notes) {
			//pour la matiere courante
			if(note.getMatiere()==controleur.getMatiereCourante()) {
				eleve = note.getEleve();
				if(!notesByEleves.containsKey(eleve))
					notesByEleves.put(eleve, new ArrayList<Note>());
				
				//eleve => note
				notesByEleves.get(eleve).add(note);
				
				//maj periode-numControleMax
				int periode = note.getPeriode();
				if(!numControleByPeriode.containsKey(periode))
					numControleByPeriode.put(periode, 0);
				if(numControleByPeriode.get(periode) < note.getNumControle())
					numControleByPeriode.put(periode, note.getNumControle());
			}
		}
		
		//liste des eleves de la classe
		eleves = new ArrayList<Eleve>(classeCourante.getEleves());
		
		//maj numControleMax si il est superieur a celui deja connu avec numControleMax
		//int periodeMax = Collections.max(numControleByPeriode.keySet());
		//int numControleMax = numControleByPeriode.get(periodeMax);
		nbColumnsForNote=0;
		for (int periode : numControleByPeriode.keySet()) {
			nbColumnsForNote += numControleByPeriode.get(periode);
		}
		
		fireTableStructureChanged();
		fireTableRowsUpdated(0, getRowCount());
	}
	
	public int getRowCount() {
		if(controleur.getClasseCourante()==null)
			return 0;
			return eleves.size();
		}
	
	public int getColumnCount() {
		//juste nom et prénom
		if(controleur.getClasseCourante()==null || controleur.getMatiereCourante()==null) {
			return 2;
		}
		return entetes.size()+nbColumnsForNote+nbControleAdded;
	}

	public String getColumnName(int columnIndex) {
		if(columnIndex >= 3) {
			int ret[] = getColumnInfo(columnIndex);
			return Tools.getMoisCourtParPeriode(ret[0])+" "+ret[1];
		}
		return entetes.get(columnIndex);
	}

	/**
	 * @param columnIndex numéro de la colonne fournit par les fonctions model
	 * @return periode et numControle
	 */
	private int[] getColumnInfo(int columnIndex) {
		int column = columnIndex - 2;//indice => numControle
		int ret[] = new int[2];

		Set<Integer> keys = numControleByPeriode.keySet();
		int numControle;
		int numControlePassed = 0;
		for (Integer periode : keys) {
			numControle = numControleByPeriode.get(periode);
			if(column > numControle+numControlePassed) {
				numControlePassed += numControle;
				continue;
			}
			//on a trouvé la bonne periode
			ret[0] = periode;
			ret[1] = column - numControlePassed;
			return ret;
		}
		//on a dépassé les controles connus,
		//sachant que numControlePassed represente les numControle en base
		ret[0] = Tools.getPeriodeCourante();
		int periodeMax = numControleByPeriode.keySet().size()==0?
				//pas de periode, on renvoie 0 et donc forcément periodeMax != ret[0]
				0:
				//on recupere la periode max
				Collections.max(numControleByPeriode.keySet());
		ret[1] = periodeMax == ret[0] ?
				//periode courante = connu, on recup le numControleMax + difference avec column
				numControleByPeriode.get(periodeMax)+column-numControlePassed
				//sinon on est pas dans la periode demandé, difference avec column
				: column-numControlePassed;
		return ret;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Class getColumnClass(int columnIndex){
		return String.class;
	}

	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		//pour toutes les cellules hors note, on ignore
		if(aValue == null && columnIndex-3 < 0) {
			super.setValueAt(getValueAt(rowIndex, columnIndex), rowIndex, columnIndex);
			return;
		}
		//on va parser la note
		float noteValue;
		try {
			noteValue = Float.valueOf(aValue.toString());
		} catch (NumberFormatException e) {
			//mauvaise valeur entrée, on ne modifie pas nos notes
			super.setValueAt(getValueAt(rowIndex, columnIndex), rowIndex, columnIndex);
			return;
		}
		//si valeur negative, on ne fait rien
		if(noteValue<0 || noteValue > 20) {
			super.setValueAt(getValueAt(rowIndex, columnIndex), rowIndex, columnIndex);
			return;
		}

		//checks valides, on va ajouter la note
		boolean found = false;
		int ret[] = getColumnInfo(columnIndex);
		int periode = ret[0];
		int numControle = ret[1];

		Eleve eleve = eleves.get(rowIndex);
		if(!notesByEleves.containsKey(eleve)) {
			notesByEleves.put(eleve, new ArrayList<Note>());
		} else {
			//pour toutes les notes connus de l'eleve car il en a déjà
			for (Note note : notesByEleves.get(eleve)) {
				if(note.getNumControle()==numControle && note.getPeriode()==periode) {
					//trouvé pour le numControle demandé
					try {
						controleur.updateNote(note, noteValue);
						found = true;
						refresh();
						//pour quitter la fonction
						return;
					} catch (LogicException e) {
						//ne devrait jamais arriver
						e.printStackTrace();
						System.err.println("Note invalide en cache");
					}
				}
			}
		}
		if(!found) {
			controleur.addNote(eleves.get(rowIndex), periode, numControle, noteValue);
			//si l'indice est supérieur au nb controle connu + 2 pour nom-prenom-moyenne
			if(columnIndex > nbColumnsForNote+2) {
				//les colonnes entre celle edité (incluse) et autres nouvelles = definitives
				nbControleAdded -= columnIndex-(nbColumnsForNote+2);
			}
			refresh();
		}
	}

	public Object getValueAt(int rowIndex, int columnIndex) {
		Eleve eleve = null;
		switch(columnIndex){
			case 0:
				return eleves.get(rowIndex).getNom();
			case 1:
				return eleves.get(rowIndex).getPrenom();
			case 2:
				eleve = eleves.get(rowIndex);
				//si l'eleve n'a pas de notes, moyenne = vide
				if(eleve==null || !notesByEleves.containsKey(eleve))
					return "";

				//demande de la moyenne
				List<Note> notes = notesByEleves.get(eleve);
				if(notes==null) {
					System.err.println("notes inconnus pour l'eleve "+eleves.get(rowIndex)+" nbColonnes: "
									+getColumnCount()+" et matiereCourante: "+matiereCourante);
					return "";
				}
				float moyenne = 0;
				int nbNotes = 0;
				for (Note note : notes) {
					moyenne += note.getNote();
					nbNotes++;
				}
				if(nbNotes==0)
					//affichage vide si aucune note
					return "";
				return Float.valueOf(moyenne/nbNotes).toString();
			default:
				//controle demandé = columnIndex - prenom - nom -
				int ret[] = getColumnInfo(columnIndex);
				int periode = ret[0];
				int numControleDemande = ret[1];
				//on renvoie la note si on la trouve
				eleve = eleves.get(rowIndex);
				if(eleve==null || !notesByEleves.containsKey(eleve))
					return "";
				for (Note note : notesByEleves.get(eleve))
					if(periode==note.getPeriode() && note.getNumControle()==numControleDemande)
						return String.valueOf(note.getNote());
				//sinon chaine vide
				return "";
		}
	}

	@Override
	public boolean isCellEditable(int row, int col){
		//tout sauf nom-prenom-moyenne
		if(col >= 3)
			return true;
		else
			return false;
	}

	/**
	 * rajoute une colonne avec le numControle max + 1
	 */
	public void newControle() {
		int maxControleCourant = 0;
		if(numControleByPeriode.keySet().contains(Tools.getPeriodeCourante())) {
			//periode courante connue, on recupére les numControle existant
			maxControleCourant = numControleByPeriode.get(Tools.getPeriodeCourante());
		}
		//pas plus de 4 controles par periode
		if((nbControleAdded + maxControleCourant) < 4) {
			nbControleAdded++;
			//refresh notre liste
			refresh();
		}
	}
	
	/**
	 * supprime le dernier controle
	 */
	public void removeControle(int i) {
		//on enleve de notre source tout ce qui est du dernier numControle
		int ret[] = getColumnInfo(getColumnCount()-1);
		int periode = ret[0];
		int numControle = ret[1];
		//maj pour supprimer numControleMax
		if(nbControleAdded>0)
			nbControleAdded--;
		else
			//on ne permet pas de détruire les anciennes colonnes
			return;
		try {
			controleur.deleteNoteByNumControle(periode, numControle);
		} catch (LogicException e) {
			e.printStackTrace();
		}
		//refresh notre liste
		refresh();
	}
}
